﻿/*
 * FaceRecognition.cs
 * 
 * 人脸识别模块
 * 
 * By developer_ken
 */
using OpenCvSharp;
using OpenCvSharp.Face;
using System;
using System.Collections.Generic;

namespace SmGateDemo
{
    public class FaceRecognition
    {
        readonly CascadeClassifier Facelocator;
        readonly FaceRecognizer Facerecog;
        bool DEBUG;
        public FaceRecognition(double threshould)
        {
            DEBUG = Environment.GetEnvironmentVariable("SMG_DEBUG") != null && Environment.GetEnvironmentVariable("SMG_DEBUG").Length > 0;
            Facelocator = new CascadeClassifier("./mod/face_locator.xml");
            Facerecog = FisherFaceRecognizer.Create(threshold: threshould);
        }

        public Rect[] LocateFaces(Mat frame)
        {
            return Facelocator.DetectMultiScale(PreProcessFrame(frame, reSize: false), flags: HaarDetectionTypes.DoCannyPruning);
        }

        public Mat[] GetFacePictures(Mat frame, Rect[] rect)
        {
            List<Mat> facepics = new List<Mat>();
            foreach (Rect loc in rect)
            {
                facepics.Add(frame.SubMat(loc));
            }
            return facepics.ToArray();
        }

        public Mat[] GetFacePictures(Mat frame)
        {
            return GetFacePictures(frame, LocateFaces(frame));
        }

        public Mat GetFacePicture(Mat frame)
        {
            var locations = LocateFaces(frame);
            Rect result = new Rect(0, 0, 0, 0);
            foreach (var res in locations)
            {
                if ((res.Width * res.Height) > (result.Width * result.Height))
                {
                    result = res;
                }
            }
            if (result.Width == 0) return null;
            return GetFacePictures(frame, new Rect[] { result })[0];
        }

        public static bool IsGrayed(Mat frame)
        {
            return frame.Channels() == 1;
        }

        public void RegisterFaces(Dictionary<Mat, int> kvpair)
        {
            var loctmp = new Dictionary<Mat, int>();
            foreach (KeyValuePair<Mat, int> item in kvpair)
            {
                var face = GetFacePicture(item.Key);
                /*
                if (faces.Length < 1) { throw new Exception("ID=" + item.Value + " 没有可识别的人脸"); }
                if (faces.Length > 1) { throw new Exception("ID=" + item.Value + " 不止一个人脸"); }
                */
                if (face == null) continue;
                loctmp.Add(PreProcessFrame(face), item.Value);
            }
            Facerecog.Train(loctmp.Keys, loctmp.Values);
        }

        public struct Prediction
        {
            public int Lable;
            public double Confidence;
        }

        public Prediction[] GetFaceIds(Mat frame)
        {
            var faces = GetFacePictures(frame);
            List<Prediction> faceIds = new List<Prediction>();
            foreach (var f in faces)
            {
                var face = PreProcessFrame(f);
                //face.SaveImage("tmp.png");
                Facerecog.Predict(face, out int lable, out double confi);
                Prediction pred = new Prediction() { Confidence = confi, Lable = lable };
                faceIds.Add(pred);
            }
            return faceIds.ToArray();
        }

        public Prediction GetFaceId(Mat frame)
        {
            var face = GetFacePicture(frame);
            if (face == null) return new Prediction() { Confidence = 0, Lable = -1 };
            var face_processed = PreProcessFrame(face);
            Facerecog.Predict(face_processed, out int lable, out double confi);
            return new Prediction() { Confidence = confi, Lable = lable };
        }

        public static Mat PreProcessFrame(Mat frame, bool toGray = true, bool reSize = true)
        {
            var f = frame;
            if (toGray && !IsGrayed(f)) { f = f.CvtColor(ColorConversionCodes.RGB2GRAY); }
            if (reSize) { f = f.Resize(new Size(200, 200)); }
            return f;
        }
    }
}
